package com.willy.ratingbar;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.PageSlider;
import ohos.app.Context;
import ohos.global.resource.RawFileEntry;
import ohos.global.resource.ResourceManager;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by willy on 2017/5/5.
 */

public class BaseRatingBar extends DirectionalLayout implements SimpleRatingBar, Component.TouchEventListener {

    private static final String FORMAT = "image/jpg";

    private int mNumStars;
    private int mPadding = 20;
    private int mStarWidth;
    private int mStarHeight;
    private float mMinimumStars = 0;
    private float mRating = -1;
    private float mStepSize = 1f;
    private float mPreviousRating = 0;
    private float mNmu = 10;

    private boolean mIsIndicator = false;
    private boolean mIsScrollable = true;
    private boolean mIsClickable = true;
    private boolean mClearRatingEnabled = true;

    private float mStartX;
    private float mStartY;

    private PixelMap mEmptyDrawable;
    private PixelMap mFilledDrawable;

    private OnRatingChangeListener mOnRatingChangeListener;

    protected List<PartialView> mPartialViews;

    public BaseRatingBar(Context context, AttrSet attrs) {
        this(context, attrs, "0");
    }

    /**
     * @param context      context
     * @param attrs        attributes from XML => app:mainText="mainText"
     * @param defStyleAttr attributes from default style (Application theme or theme)
     */
    public BaseRatingBar(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(HORIZONTAL);
        initParamsValue(attrs, context);
        verifyParamsValue();
        initRatingView();
        float rating = 0;
        if (isPresent(attrs, "srb_rating")) {
            rating = attrs.getAttr("srb_rating").get().getFloatValue();
        }
        setRating(rating);
        setTouchEventListener(this);
    }

    private void initParamsValue(AttrSet attrs, Context context) {
        mEmptyDrawable = getPixelMap("resources/rawfile/empty.png");
        mFilledDrawable = getPixelMap("resources/rawfile/filled.png");
        if (isPresent(attrs, "srb_numStars")) {
            mNumStars = attrs.getAttr("srb_numStars").get().getIntegerValue();
        }
        if (isPresent(attrs, "srb_starPadding")) {
            mPadding = attrs.getAttr("srb_starPadding").get().getDimensionValue();
        }
        if (isPresent(attrs, "srb_starWidth")) {
            mStarWidth = attrs.getAttr("srb_starWidth").get().getDimensionValue();
        }
        if (isPresent(attrs, "srb_starHeight")) {
            mStarHeight = attrs.getAttr("srb_starHeight").get().getDimensionValue();
        }
        if (isPresent(attrs, "srb_stepSize")) {
            mStepSize = attrs.getAttr("srb_stepSize").get().getFloatValue();
        }
        if (isPresent(attrs, "srb_minimumStars")) {
            mMinimumStars = attrs.getAttr("srb_minimumStars").get().getFloatValue();
        }
        if (isPresent(attrs, "srb_isIndicator")) {
            mIsIndicator = attrs.getAttr("srb_isIndicator").get().getBoolValue();
        }
        if (isPresent(attrs, "srb_scrollable")) {
            mIsScrollable = attrs.getAttr("srb_scrollable").get().getBoolValue();
        }
        if (isPresent(attrs, "srb_clickable")) {
            mIsClickable = attrs.getAttr("srb_clickable").get().getBoolValue();
        }
        if (isPresent(attrs, "srb_clearRatingEnabled")) {
            mClearRatingEnabled = attrs.getAttr("srb_clearRatingEnabled").get().getBoolValue();
        }
    }

    private boolean isPresent(AttrSet attrs, String name) {
        return attrs.getAttr(name).isPresent();
    }

    private void verifyParamsValue() {
        if (mNumStars <= 0) {
            mNumStars = 5;
        }
        if (mPadding < 0) {
            mPadding = 0;
        }
        if (mEmptyDrawable == null) {
            mEmptyDrawable = getPixelMap("resources/rawfile/empty.png");
        }
        if (mFilledDrawable == null) {
            mFilledDrawable = getPixelMap("resources/rawfile/filled.png");
        }
        if (mStepSize > 1.0f) {
            mStepSize = 1.0f;
        } else if (mStepSize < 0.1f) {
            mStepSize = 0.1f;
        }
        mMinimumStars = RatingBarUtils.getValidMinimumStars(mMinimumStars, mNumStars, mStepSize);
    }

    private PixelMap getPixelMap(String rawfile) {
        ResourceManager resManager = getResourceManager();
        RawFileEntry rawFileEntry = resManager.getRawFileEntry(rawfile);
        try {
            InputStream resource = rawFileEntry.openRawFile();
            ImageSource imageSource = ImageSource.create(resource, null);
            return imageSource.createPixelmap(null);
        } catch (IOException e) {
            System.out.println(e);
        }
        return null;
    }

    private void initRatingView() {
        mPartialViews = new ArrayList<>();
        for (int pos = 1; pos <= mNumStars; pos++) {
            PartialView partialView = getPartialView(pos, mStarWidth, mStarHeight, mPadding, mFilledDrawable, mEmptyDrawable);
            addComponent(partialView);
            mPartialViews.add(partialView);
        }
    }

    private PartialView getPartialView(final int partialViewId, int starWidth, int starHeight, int padding,
                                       PixelMap filledDrawable, PixelMap emptyDrawable) {
        PartialView partialView = new PartialView(getContext(), partialViewId, starWidth, starHeight, padding);
        partialView.setFilledDrawable(filledDrawable);
        partialView.setEmptyDrawable(emptyDrawable);
        return partialView;
    }

    /**
     * Retain this method to let other RatingBar can custom their decrease animation.
     */
    protected void emptyRatingBar() {
        fillRatingBar(0);
    }

    /**
     * the view which id is 3 also need to be filled
     *
     * @param rating rating
     */
    protected void fillRatingBar(final float rating) {
        double maxIntOfRating = Math.ceil(rating);
        for (PartialView partialView : mPartialViews) {
            int ratingViewId = (int) partialView.getTag();
            if (ratingViewId > maxIntOfRating) {
                partialView.setEmpty();
                continue;
            } else {
                partialView.setFilled();
            }
        }
    }

    @Override
    public void setNumStars(int numStars) {
        if (numStars <= 0) {
            return;
        }
        mPartialViews.clear();
        removeAllComponents();
        mNumStars = numStars;
        initRatingView();
    }

    @Override
    public int getNumStars() {
        return mNumStars;
    }

    @Override
    public void setRating(float rating) {
        setRating(rating, false);
    }

    private void setRating(float rating, boolean fromUser) {
        if (rating > mNumStars) {
            rating = mNumStars;
        }
        if (rating < mMinimumStars) {
            rating = mMinimumStars;
        }

        BigDecimal bignum1 = new BigDecimal(mRating);
        BigDecimal bignum2 = new BigDecimal(rating);
        float value = bignum1.subtract(bignum2).floatValue();
        if (Math.abs(value) <= 0) {
            return;
        }
        // Respect Step size. So if the defined step size is 0.5, and we're attributing it a 4.7 rating,
        // it should actually be set to `4.5` rating.
        mRating = Double.valueOf(Math.floor(rating / mStepSize)).floatValue() * mStepSize;
        if (mOnRatingChangeListener != null) {
            mOnRatingChangeListener.onRatingChange(this, mRating, fromUser);
        }
        fillRatingBar(mRating);
    }

    @Override
    public float getRating() {
        return mRating;
    }

    @Override
    public void setStarWidth(int starWidth) {
        mStarWidth = starWidth;
        for (PartialView partialView : mPartialViews) {
            partialView.setStarWidth(starWidth);
        }
    }

    @Override
    public int getStarWidth() {
        return mStarWidth;
    }

    @Override
    public void setStarHeight(int starHeight) {
        mStarHeight = starHeight;
        for (PartialView partialView : mPartialViews) {
            partialView.setStarHeight(starHeight);
        }
    }

    @Override
    public int getStarHeight() {
        return mStarHeight;
    }

    @Override
    public void setStarPadding(int ratingPadding) {
        if (ratingPadding < 0) {
            return;
        }

        mPadding = ratingPadding;

        for (PartialView partialView : mPartialViews) {
            partialView.setPadding(mPadding, mPadding, mPadding, mPadding);
        }
    }

    @Override
    public int getStarPadding() {
        return mPadding;
    }

    @Override
    public void setEmptyDrawableRes(int res) {

        PixelMap drawable = getPixelMapFromResource(getContext(), res);
        if (drawable != null) {
            setEmptyDrawable(drawable);
        }
    }

    private PixelMap getPixelMapFromResource(Context context, int resId) {
        PixelMap pixelmap;
        try (InputStream drawableInputStream = context.getResourceManager().getResource(resId)) {
            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            sourceOptions.formatHint = FORMAT;
            ImageSource imageSource = ImageSource.create(drawableInputStream, sourceOptions);
            pixelmap = imageSource.createPixelmap(null);
            return pixelmap;
        } catch (Exception e) {
            e.getMessage();
        }
        return null;
    }

    @Override
    public void setFilledDrawableRes(int res) {
        PixelMap drawable = getPixelMapFromResource(getContext(), res);
        if (drawable != null) {
            setFilledDrawable(drawable);
        }
    }

    @Override
    public void setEmptyDrawable(PixelMap drawable) {
        mEmptyDrawable = drawable;
        for (PartialView partialView : mPartialViews) {
            partialView.setEmptyDrawable(drawable);
        }
    }

    @Override
    public void setFilledDrawable(PixelMap drawable) {
        mFilledDrawable = drawable;
        for (PartialView partialView : mPartialViews) {
            partialView.setFilledDrawable(drawable);
        }
    }

    @Override
    public void setMinimumStars(float minimumStars) {
        mMinimumStars = RatingBarUtils.getValidMinimumStars(minimumStars, mNumStars, mStepSize);
    }

    @Override
    public boolean isIndicator() {
        return mIsIndicator;
    }

    @Override
    public void setIsIndicator(boolean indicator) {
        mIsIndicator = indicator;
    }

    @Override
    public boolean isScrollable() {
        return mIsScrollable;
    }

    @Override
    public void setScrollable(boolean scrollable) {
        mIsScrollable = scrollable;
    }

    @Override
    public boolean isClickable() {
        return mIsClickable;
    }

    @Override
    public void setClickable(boolean clickable) {
        this.mIsClickable = clickable;
    }

    @Override
    public void setClearRatingEnabled(boolean enabled) {
        this.mClearRatingEnabled = enabled;
    }

    @Override
    public boolean isClearRatingEnabled() {
        return mClearRatingEnabled;
    }

    @Override
    public float getStepSize() {
        return mStepSize;
    }

    @Override
    public void setStepSize(float stepSize) {
        this.mStepSize = stepSize;
    }


    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        if (isIndicator()) {
            return false;
        }
        float eventX = event.getPointerPosition(0).getX();
        float eventY = event.getPointerPosition(0).getY();
        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                mStartX = eventX;
                mStartY = eventY;
                mPreviousRating = mRating;
                TouchEventManager.getInstance().getResultCallback().onTouch();
                break;
            case TouchEvent.POINT_MOVE:
                TouchEventManager.getInstance().getResultCallback().onTouch();
                if (!isScrollable()) {
                    return false;
                }
                handleMoveEvent(eventX);
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                TouchEventManager.getInstance().getResultCallback().onCancel();
                if (!RatingBarUtils.isClickEvent(mStartX, mStartY, event) || !isClickable()) {
                    return false;
                }
                handleClickEvent(eventX);
            case TouchEvent.CANCEL:
                TouchEventManager.getInstance().getResultCallback().onCancel();
                break;
        }
        return true;
    }

    private void handleMoveEvent(float eventX) {
        for (PartialView partialView : mPartialViews) {
            BigDecimal bignum1 = new BigDecimal(partialView.getWidth());
            BigDecimal bignum2 = new BigDecimal(mNmu);
            BigDecimal bignum3 = new BigDecimal(mMinimumStars);
            BigDecimal multiply = bignum1.divide(bignum2);
            BigDecimal multiply1 = bignum3.multiply(bignum1);
            float temp = multiply.add(multiply1).floatValue();
            if (eventX < temp) {
                setRating(mMinimumStars, true);
                return;
            }
            if (!isPositionInRatingView(eventX, partialView)) {
                continue;
            }
            float rating = RatingBarUtils.calculateRating(partialView, mStepSize, eventX);

            BigDecimal bignum4 = new BigDecimal(mRating);
            BigDecimal bignum5 = new BigDecimal(rating);
            float floatValue = bignum4.subtract(bignum5).floatValue();
            if (Math.abs(floatValue) > 0) {
                setRating(rating, true);
            }
        }
    }

    private void handleClickEvent(float eventX) {
        for (PartialView partialView : mPartialViews) {
            if (!isPositionInRatingView(eventX, partialView)) {
                continue;
            }

            float rating;
            if (mStepSize == 1) {
                rating = (int) partialView.getTag();
            } else {
                rating = RatingBarUtils.calculateRating(partialView, mStepSize, eventX);
            }
            if (mPreviousRating == rating && isClearRatingEnabled()) {
                setRating(mMinimumStars, true);
            } else {
                setRating(rating, true);
            }
            break;
        }
    }

    private boolean isPositionInRatingView(float eventX, Component ratingView) {
        return eventX > ratingView.getLeft() && eventX < ratingView.getRight();
    }

    public interface OnRatingChangeListener {
        void onRatingChange(BaseRatingBar ratingBar, float rating, boolean fromUser);
    }

    public void setOnRatingChangeListener(OnRatingChangeListener onRatingChangeListener) {
        mOnRatingChangeListener = onRatingChangeListener;
    }
}